﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using net.entity.framework;
using net.entity.framework.cache;
using net.entity.framework.common;
using net.entity.framework.db;
using net.entity.framework.query;

namespace net.entity.framework {
    /// <summary>
    /// Pre-fetches the chunk of Entities
    /// </summary>
    public sealed class Prefetcher {
        private static int chunkSize = 5000;

        public static void RunAndCache(EntityKey key, QueryContext context) {
            var items = Run(key, context).ToList();
            EntityCache.Instance.PopulateTargets(items);
        }

        /// <summary>
        /// Fetch next chunk of Entities
        /// This is assuming that most of the opetaions are forward only
        /// and there is no back traversal.
        /// </summary>
        /// <param name="key"></param>
        /// <param name="context"></param>
        /// <returns></returns>
        public static IEnumerable<KeyValuePair<EntityKey, Entity>> Run(EntityKey key, QueryContext context) {
            var startTime = DateTime.Now;
            var entityInfo = TypeIdCache.GetValue(key.TypeId);
            DataSet ds;
            //NOTE: I can do this better, alrady have a query - probably run an execute over it
            var commandText = GetCommand(key, context, entityInfo);
            using (var provider = new DataProvider(DbClient.SqlClient)) {
                ds = Repository.ExecuteQuery(provider, commandText);
            }
            var resolvedItems = TypeSpecificResolver.Resolve(ds.Tables[0]);
            var endTime = DateTime.Now;
            var duration = endTime.Subtract(startTime);
            Console.WriteLine("\nPrefetcher: Just resolved {0} item(s) of type '{1}' [{2}:{3}:{4}:{5}]",
                              resolvedItems.Count, entityInfo.EntityType.Name,
                              duration.Hours, duration.Minutes, duration.Seconds, duration.Milliseconds);
            return resolvedItems;
        }

        /// <summary>
        /// Gets the command for query
        /// </summary>
        /// <param name="key"></param>
        /// <param name="queryContext"></param>
        /// <param name="entityInfo"></param>
        /// <returns></returns>
        private static string GetCommand(EntityKey key, QueryContext queryContext, EntityInfo entityInfo) {
            var entityRefType = entityInfo.EntityRefType;
            var rootRef = EqlEntityRefCache.LookupOrCreate(entityRefType).EqlEntityRef;
            var typeIdTerminal = rootRef.GetPropertyValue<Terminal>("TypeId", true);
            var idTerminal = rootRef.GetPropertyValue<Terminal>("Id", true);
            if (queryContext == null) {
                var whereClause = typeIdTerminal == key.TypeId; //restrict the scope of query
                whereClause &= idTerminal == key.id;
                return Eql.Select(rootRef.Star).Where(whereClause).ToCommandText();
            }
            //run the query and send the result
            var chunkingClause = idTerminal >= key.id;
            return Eql.Select(rootRef.Star).Where(queryContext.state.WhereClause && chunkingClause)
                .GroupBy(queryContext.state.GroupByRoots)
                .Having(queryContext.state.HavingClause)
                .OrderBy(queryContext.state.OrderByRoots)
                .ToCommandText();
        }
    }
}